//Copyright (C) 2011 Ron Coleman. Contact: ronncoleman@gmail.com
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 3 of the License, or (at your option) any later version.
//
//This library is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this library; if not, write to the Free Software
//Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

package bj.actor
import scala.actors.Actor
import scala.actors.OutputChannel
import bj.card.Hand
import bj.hkeeping.Ok
import bj.card.Card
import bj.util.Logs
import bj.hkeeping.Broke

/** This object represents the player's class variables */
object Player {
  /** Unique id counter for players */
  var id: Int = -1

  /** Creates, starts, and send the players on their way.  */
  def start(players : List[Player]) {   
    players.foreach { p =>
      p.start
      
      p ! Go
    }
  }
}

/**
 * This class implements the functionality of a player.
 * @param name Name of the player
 * @param bankroll Bankroll of the player to start
 * @param betAmt Minimum amount player will bet
 */
abstract class Player(name: String, var bankroll: Double, var betAmt: Double) extends Actor with Hand with Logs {
  /**
   * Get the player's unique id
   * Note: this assumes players are constructed serially!
   */
  Player.id += 1  
  val pid = Player.id
  
  val playerBank = bankroll
  	  

  /** Dealer's up-card */
  var upcard : Card = _

  /** Table id I've been assigned */
  var tableId : Int = -1

  /** Pretty-prints the player reference */
  override def toString: String = "(" + name + ", " + pid + ")"

  /** This method receives messages */
  def act {
    loop {
      react {
         
        // Receives message to tell player to place its bet
        case Go =>
          debug(this+" received Go placing bet = "+betAmt+" from bankroll = "+bankroll)
          bet
          
        // Receives the dealer's up-card which is player's cue to play
        case Up(card) =>
          debug(this + " received dealer's up card = " + card)

          play(card)

        // Receives a card from the dealer
        case card: Card =>         
          hitMe(card)

        // Receives broke message
        case Broke =>
          debug(this+ " received BROKE")
          clearHand()
        // Receives message about dealt card
        case Observe(card,player,shoeSize) =>
          debug(this+" observed: "+card)
          observe(card,player,shoeSize)
          
        // Receives the table number I've been assigned to
        case TableNumber(tid : Int) =>
          debug(this+" received table assignment tid = "+tid)
          assign(tid)
        
        case Win(gain) =>
          val won = betAmt * gain
          
          bankroll += won
          clearHand()
          
          debug(this+" received WIN " + won + " new bankroll = "+bankroll)
          
        case Loose(gain) =>
          val lost = betAmt * gain
          
          bankroll += lost
          clearHand()
          
          debug(this+" received LOOSE " + lost + " new bankroll = "+bankroll)          
          
        case Push(gain) =>
          debug(this+" received PUSH bankroll = "+bankroll)
          clearHand()
          
        // Receives an ACK
        case Ok =>
          debug(this + " received Ok")

        // Receives something completely from left field
        case dontKnow =>
          // Got something we REALLY didn't expect
          debug(this+" received unexpected: "+dontKnow)
      }
    }

  }

  /**
   * Processes table numbers.
   * @param tid Table id
   */
  def assign(tid : Int) {
    
  }
  
  /**
   * Observes a card being dealt.
   * Note: This method needs to be overridden if counting cards.
   * @param card Card the player received 
   * @param player Player id receiving this card
   * @param size Shoe size
   */
  def observe(card : Card, player : Int, size : Int) {
    
  }
  
  /**
   * Processes hit request.
   * @param dealer Dealer's mailbox
   * @param upcard Dealer's up-card
   */  
  def hitMe(card : Card) {
    // Hit my hand with this card
    this.hit(card)
    
    debug(this + " received card " + card + " hand sz = " + cards.size + " value = " + value)          

    // If I've received more than two cards, the extras must be in
    // response to my requests
    if (cards.size > 2)
      play(this.upcard)    
  }
  
  /** Places a bet with the house */
  def bet {
    if (bankroll < betAmt)
      return

    House ! Bet(pid, betAmt)
  }
  
  /**
   * Processes the dealer's upcard.
   * @param dealer Dealer's mailbox
   * @param upcard Dealer's up-card
   */
  def play(upcard : Card) {
    this.upcard = upcard

    // Compute my play strategy
    val request = analyze(upcard)  //this will be commented out and used for bot players only
    
    if(request == DoubleDown(pid)) {
      betAmt *= 2
      debug(this + " doubleDownRequest = " + request + " betAmt is now: " + betAmt)
    }
      

    debug(this + " request = " + request)

    // Don't send a request if we break since
    // dealer will have moved on
    if(!this.broke)
    	sender ! request
    
  }

def analyze(upcard : Card) : Request  
  
  /** Analyzes my best play using a condensed form of Basic Strategy. */
 /* 
  def analyze(upcard : Card) : Request = {
    // If my hand >= 17, we're staying
    if (value >= 17)
      return Stay(pid)

    // If I have ten or less, no harm in hitting
    if (value <= 10)
      return Hit(pid)

    // If the dealer can bust, we're staying
    if (upcard.value >= 2 && upcard.value <= 6)
      return Stay(pid)

    // Dealer must be showing, A, 7, 8, 9, or 10 so...
    // we must hit
    return Hit(pid)
  }

  */
  
  /*
  /** Analyzes my best play using a condensed form of Basic Strategy. */
  def analyze(upcard: Card): Request = {

    val hard = new Array[Array[String]](22, 12)
    hard(5)(2) = "Hit"; hard(5)(3) = "Hit"; hard(5)(4) = "Hit"; hard(5)(5) = "Hit"; hard(5)(6) = "Hit"; hard(5)(7) = "Hit"; hard(5)(8) = "Hit"; hard(5)(9) = "Hit"; hard(5)(10) = "Hit"; hard(5)(11) = "Hit"
    hard(6)(2) = "Hit"; hard(6)(3) = "Hit"; hard(6)(4) = "Hit"; hard(6)(5) = "Hit"; hard(6)(6) = "Hit"; hard(6)(7) = "Hit"; hard(6)(8) = "Hit"; hard(6)(9) = "Hit"; hard(6)(10) = "Hit"; hard(6)(11) = "Hit"
    hard(7)(2) = "Hit"; hard(7)(3) = "Hit"; hard(7)(4) = "Hit"; hard(7)(5) = "Hit"; hard(7)(6) = "Hit"; hard(7)(7) = "Hit"; hard(7)(8) = "Hit"; hard(7)(9) = "Hit"; hard(7)(10) = "Hit"; hard(7)(11) = "Hit"
    hard(8)(2) = "Hit"; hard(8)(3) = "Hit"; hard(8)(4) = "Hit"; hard(8)(5) = "Hit"; hard(8)(6) = "Hit"; hard(8)(7) = "Hit"; hard(8)(8) = "Hit"; hard(8)(9) = "Hit"; hard(8)(10) = "Hit"; hard(8)(11) = "Hit"
    hard(9)(2) = "Hit"; hard(9)(3) = "DoubleDown"; hard(9)(4) = "DoubleDown"; hard(9)(5) = "DoubleDown"; hard(9)(6) = "DoubleDown"; hard(9)(7) = "Hit"; hard(9)(8) = "Hit"; hard(9)(9) = "Hit"; hard(9)(10) = "Hit"; hard(9)(11) = "Hit"
    hard(10)(2) = "DoubleDown"; hard(10)(3) = "DoubleDown"; hard(10)(4) = "DoubleDown"; hard(10)(5) = "DoubleDown"; hard(10)(6) = "DoubleDown"; hard(10)(7) = "DoubleDown"; hard(10)(8) = "DoubleDown"; hard(10)(9) = "DoubleDown"; hard(10)(10) = "Hit"; hard(10)(11) = "Hit"
    hard(11)(2) = "DoubleDown"; hard(11)(3) = "DoubleDown"; hard(11)(4) = "DoubleDown"; hard(11)(5) = "DoubleDown"; hard(11)(6) = "DoubleDown"; hard(11)(7) = "DoubleDown"; hard(11)(8) = "DoubleDown"; hard(11)(9) = "DoubleDown"; hard(11)(10) = "Hit"; hard(11)(11) = "Hit"
    hard(12)(2) = "Hit"; hard(12)(3) = "Hit"; hard(12)(4) = "Stay"; hard(12)(5) = "Stay"; hard(12)(6) = "Stay"; hard(12)(7) = "Hit"; hard(12)(8) = "Hit"; hard(12)(9) = "Hit"; hard(12)(10) = "Hit"; hard(12)(11) = "Hit"
    hard(13)(2) = "Stay"; hard(13)(3) = "Stay"; hard(13)(4) = "Stay"; hard(13)(5) = "Stay"; hard(13)(6) = "Stay"; hard(13)(7) = "Hit"; hard(13)(8) = "Hit"; hard(13)(9) = "Hit"; hard(13)(10) = "Hit"; hard(13)(11) = "Hit"
    hard(14)(2) = "Stay"; hard(14)(3) = "Stay"; hard(14)(4) = "Stay"; hard(14)(5) = "Stay"; hard(14)(6) = "Stay"; hard(14)(7) = "Hit"; hard(14)(8) = "Hit"; hard(14)(9) = "Hit"; hard(14)(10) = "Hit"; hard(14)(11) = "Hit"
    hard(15)(2) = "Stay"; hard(15)(3) = "Stay"; hard(15)(4) = "Stay"; hard(15)(5) = "Stay"; hard(15)(6) = "Stay"; hard(15)(7) = "Hit"; hard(15)(8) = "Hit"; hard(15)(9) = "Hit"; hard(15)(10) = "Hit"; hard(15)(11) = "Hit"
    hard(16)(2) = "Stay"; hard(16)(3) = "Stay"; hard(16)(4) = "Stay"; hard(16)(5) = "Stay"; hard(16)(6) = "Stay"; hard(16)(7) = "Hit"; hard(16)(8) = "Hit"; hard(16)(9) = "Hit"; hard(16)(10) = "Hit"; hard(16)(11) = "Hit"
    hard(17)(2) = "Stay"; hard(17)(3) = "Stay"; hard(17)(4) = "Stay"; hard(17)(5) = "Stay"; hard(17)(6) = "Stay"; hard(17)(7) = "Stay"; hard(17)(8) = "Stay"; hard(17)(9) = "Stay"; hard(17)(10) = "Stay"; hard(17)(11) = "Stay"
    hard(18)(2) = "Stay"; hard(18)(3) = "Stay"; hard(18)(4) = "Stay"; hard(18)(5) = "Stay"; hard(18)(6) = "Stay"; hard(18)(7) = "Stay"; hard(18)(8) = "Stay"; hard(18)(9) = "Stay"; hard(18)(10) = "Stay"; hard(18)(11) = "Stay"
    hard(19)(2) = "Stay"; hard(19)(3) = "Stay"; hard(19)(4) = "Stay"; hard(19)(5) = "Stay"; hard(19)(6) = "Stay"; hard(19)(7) = "Stay"; hard(19)(8) = "Stay"; hard(19)(9) = "Stay"; hard(19)(10) = "Stay"; hard(19)(11) = "Stay"
    hard(20)(2) = "Stay"; hard(20)(3) = "Stay"; hard(20)(4) = "Stay"; hard(20)(5) = "Stay"; hard(20)(6) = "Stay"; hard(20)(7) = "Stay"; hard(20)(8) = "Stay"; hard(20)(9) = "Stay"; hard(20)(10) = "Stay"; hard(20)(11) = "Stay"

    val soft = new Array[Array[String]](10, 12)
    soft(2)(2) = "Hit"; soft(2)(3) = "Hit"; soft(2)(4) = "Hit"; soft(2)(5) = "DoubleDown"; soft(2)(6) = "DoubleDown"; soft(2)(7) = "Hit"; soft(2)(8) = "Hit"; soft(2)(9) = "Hit"; soft(2)(10) = "Hit"; soft(2)(11) = "Hit"
    soft(3)(2) = "Hit"; soft(3)(3) = "Hit"; soft(3)(4) = "Hit"; soft(3)(5) = "DoubleDown"; soft(3)(6) = "DoubleDown"; soft(3)(7) = "Hit"; soft(3)(8) = "Hit"; soft(3)(9) = "Hit"; soft(3)(10) = "Hit"; soft(3)(11) = "Hit"
    soft(4)(2) = "Hit"; soft(4)(3) = "Hit"; soft(4)(4) = "DoubleDown"; soft(4)(5) = "DoubleDown"; soft(4)(6) = "DoubleDown"; soft(4)(7) = "Hit"; soft(4)(8) = "Hit"; soft(4)(9) = "Hit"; soft(4)(10) = "Hit"; soft(4)(11) = "Hit"
    soft(5)(2) = "Hit"; soft(5)(3) = "Hit"; soft(5)(4) = "DoubleDown"; soft(5)(5) = "DoubleDown"; soft(5)(6) = "DoubleDown"; soft(5)(7) = "Hit"; soft(5)(8) = "Hit"; soft(5)(9) = "Hit"; soft(5)(10) = "Hit"; soft(5)(11) = "Hit"
    soft(6)(2) = "Hit"; soft(6)(3) = "DoubleDown"; soft(6)(4) = "DoubleDown"; soft(6)(5) = "DoubleDown"; soft(6)(6) = "DoubleDown"; soft(6)(7) = "Hit"; soft(6)(8) = "Hit"; soft(6)(9) = "Hit"; soft(6)(10) = "Hit"; soft(6)(11) = "Hit"
    soft(7)(2) = "Stay"; soft(7)(3) = "DoubleDown"; soft(7)(4) = "DoubleDown"; soft(7)(5) = "DoubleDown"; soft(7)(6) = "DoubleDown"; soft(7)(7) = "Stay"; soft(7)(8) = "Stay"; soft(7)(9) = "Hit"; soft(7)(10) = "Hit"; soft(7)(11) = "Hit"
    soft(8)(2) = "Stay"; soft(8)(3) = "Stay"; soft(8)(4) = "Stay"; soft(8)(5) = "Stay"; soft(8)(6) = "Stay"; soft(8)(7) = "Stay"; soft(8)(8) = "Stay"; soft(8)(9) = "Stay"; soft(8)(10) = "Stay"; soft(8)(11) = "Stay"
    soft(9)(2) = "Stay"; soft(9)(3) = "Stay"; soft(9)(4) = "Stay"; soft(9)(5) = "Stay"; soft(9)(6) = "Stay"; soft(9)(7) = "Stay"; soft(9)(8) = "Stay"; soft(9)(9) = "Stay"; soft(9)(10) = "Stay"; soft(9)(11) = "Stay"

    val split = new Array[Array[String]](12, 12)
    split(2)(2) = "Hit"; split(2)(3) = "Hit"; split(2)(4) = "Split"; split(2)(5) = "Split"; split(2)(6) = "Split"; split(2)(7) = "Split"; split(2)(8) = "Hit"; split(2)(9) = "Hit"; split(2)(10) = "Hit"; split(2)(11) = "Hit"
    split(3)(2) = "Hit"; split(3)(3) = "Hit"; split(3)(4) = "Split"; split(3)(5) = "Split"; split(3)(6) = "Split"; split(3)(7) = "Split"; split(3)(8) = "Hit"; split(3)(9) = "Hit"; split(3)(10) = "Hit"; split(3)(11) = "Hit"
    split(4)(2) = "Hit"; split(4)(3) = "Hit"; split(4)(4) = "Hit"; split(4)(5) = "Hit"; split(4)(6) = "Hit"; split(4)(7) = "Hit"; split(4)(8) = "Hit"; split(4)(9) = "Hit"; split(4)(10) = "Hit"; split(4)(11) = "Hit"
    split(5)(2) = "DoubleDown"; split(5)(3) = "DoubleDown"; split(5)(4) = "DoubleDown"; split(5)(5) = "DoubleDown"; split(5)(6) = "DoubleDown"; split(5)(7) = "DoubleDown"; split(5)(8) = "DoubleDown"; split(5)(9) = "DoubleDown"; split(5)(10) = "Hit"; split(5)(11) = "Hit"
    split(6)(2) = "Hit"; split(6)(3) = "Split"; split(6)(4) = "Split"; split(6)(5) = "Split"; split(6)(6) = "Split"; split(6)(7) = "Hit"; split(6)(8) = "Hit"; split(6)(9) = "Hit"; split(6)(10) = "Hit"; split(6)(11) = "Hit"
    split(7)(2) = "Split"; split(7)(3) = "Split"; split(7)(4) = "Split"; split(7)(5) = "Split"; split(7)(6) = "Split"; split(7)(7) = "Split"; split(7)(8) = "Hit"; split(7)(9) = "Hit"; split(7)(10) = "Hit"; split(7)(11) = "Hit"
    split(8)(2) = "Split"; split(8)(3) = "Split"; split(8)(4) = "Split"; split(8)(5) = "Split"; split(8)(6) = "Split"; split(8)(7) = "Split"; split(8)(8) = "Split"; split(8)(9) = "Split"; split(8)(10) = "Split"; split(8)(11) = "Split"
    split(9)(2) = "Split"; split(9)(3) = "Split"; split(9)(4) = "Split"; split(9)(5) = "Split"; split(9)(6) = "Split"; split(9)(7) = "Stay"; split(9)(8) = "Split"; split(9)(9) = "Split"; split(9)(10) = "Stay"; split(9)(11) = "Stay"
    split(10)(2) = "Stay"; split(10)(3) = "Stay"; split(10)(4) = "Stay"; split(10)(5) = "Stay"; split(10)(6) = "Stay"; split(10)(7) = "Stay"; split(10)(8) = "Stay"; split(10)(9) = "Stay"; split(10)(10) = "Stay"; split(10)(11) = "Stay"
    split(11)(2) = "Split"; split(11)(3) = "Split"; split(11)(4) = "Split"; split(11)(5) = "Split"; split(11)(6) = "Split"; split(11)(7) = "Split"; split(11)(8) = "Split"; split(11)(9) = "Split"; split(11)(10) = "Split"; split(11)(11) = "Stay"

    if (cards.size <= 2) {
      if (cards(0).ace != cards(1).ace) { //make sure both are not ace

        if (cards(0).ace) { // first card is ace

          if (soft(cards(1).value)(upcard.value) == "Hit")
            return Hit(pid)

          if (soft(cards(1).value)(upcard.value) == "DoubleDown")
            return DoubleDown(pid)

          if (soft(cards(1).value)(upcard.value) == "Stay")
            return Stay(pid)
        }

        if (cards(1).ace) { // second card is ace then my array value is going to be first card value

          if (soft(cards(0).value)(upcard.value) == "Hit")
            return Hit(pid)

          if (soft(cards(0).value)(upcard.value) == "DoubleDown")
            return DoubleDown(pid)

          if (soft(cards(0).value)(upcard.value) == "Stay")
            return Stay(pid)
        }

      }

      if (cards(0).value == cards(1).value) { // both the cards are of same value

        if (split(cards(1).value)(upcard.value) == "Split")
          return Hit(pid)

        if (split(cards(1).value)(upcard.value) == "Hit")
          return Hit(pid)

        if (split(cards(1).value)(upcard.value) == "DoubleDown")
          return DoubleDown(pid)

        if (split(cards(1).value)(upcard.value) == "Stay")
          return Stay(pid)

      }

    }
    
    if(value <= 21) {

    if (hard(value)(upcard.value) == "Hit")
      return Hit(pid)

    if (hard(value)(upcard.value) == "Stay")
      return Stay(pid)

    if (hard(value)(upcard.value) == "DoubleDown")
      return DoubleDown(pid)
    }
    return Hit(pid)
  }
  */
  def setBankRoll(amt: Double) {
    if(amt >= 20)
      bankroll = amt
  }


  def setBet(amt: Double) {
    if(amt >= 5)
      betAmt = amt
  }
  
}